﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using UnityEngine;

namespace EvaOxygen
{
    public class EvaPartModule : PartModule
    {
        public const int ms_nStartOxygen = 30/*10800*/; //starting oxygen amount
        public const int ms_nStartRevive = 50/*600*/;   //starting revive timer
        public const int ms_nPreAlarm = 10/*600*/;      //time before out of oxygen to trigger alarm

        public KerbalEVA kerbalEva;                     //the eva kerbal
        public EvaBehaviour ourManager;                 //the behavior manager

        public bool FirstUpdate;                        //first update variable to set alarm timer

        public KFSMState myState = null;                //my state to keep kerbal in ragdoll
        public KFSMEvent fallEvent = null;              //event to transistion to my state
        public KFSMEvent recoverEvent = null;           //built in recover state



        [KSPField(guiName = "Oxygen Remaining", guiUnits = " ", guiFormat = "0:00:00", isPersistant = true, guiActive = true)]
        public int OxygenTime;                          //displayed oxygen remaining amount



        [KSPField(guiName = "Revive Time", guiUnits = " ", guiFormat = "0:00:00", isPersistant = true, guiActive = false)]
        public int ReviveTime;                          //revive timer (not shown initially)
        public bool ReviveActive;                       //whether revive is active



        [KSPField(guiName = "Freeze Time", guiUnits = " ", guiFormat = "0:00:00", isPersistant = true, guiActive = false)]
        public double LastMissionTime;                  //not displayed, but used for easy persistant storage
        public bool FreezeTime;                         //whether to freeze the time because atmosphere is present



        [KSPEvent(guiName = "Revive", guiActiveUnfocused = true, active = false, externalToEVAOnly = true, unfocusedRange = 2)]
        public void Revive()
        {
            ReviveActive = false;
            ReviveTime = 0;
            LastMissionTime = this.vessel.missionTime;
            
            Events["Revive"].active = false;

            this.Fields["OxygenTime"].guiActive = true;
            this.Fields["ReviveTime"].guiActive = false;

            kerbalEva.fsm.RunEvent(recoverEvent);

            ourManager.RevivedUnit = this;
        }



        public EvaPartModule() : base()
        {
            print("EvaOxygen : New EvaPartModule added");
            FirstUpdate = true;

            OxygenTime = 0;

            ReviveTime = 0;
            ReviveActive = false;

            FreezeTime = false;
            LastMissionTime = 0;
        }

        public override void OnLoad(ConfigNode node)
        {
            base.OnLoad(node);

            //find kerbalEva partModule
            foreach (PartModule module in this.part.Modules)
            {
                if (module && module.GetType() == typeof(KerbalEVA))
                {
                    kerbalEva = (KerbalEVA)module;
                    break;
                }
            }

            //make state that keeps kerbal in ragdoll
            myState = new KFSMState("FallDown");
            myState.updateMode = KFSMUpdateMode.UPDATE;
            myState.OnEnter = new KFSMStateChange(StateEnter);
            myState.OnUpdate = new KFSMCallback(StateUpdate);
            myState.OnLeave = new KFSMStateChange(StateLeave);
            kerbalEva.fsm.AddState(myState);

            //make event that sends kerbal to our state
            fallEvent = new KFSMEvent("FallDownABunch");
            fallEvent.GoToStateOnEvent = myState;
            fallEvent.updateMode = KFSMUpdateMode.MANUAL_TRIGGER;
            kerbalEva.fsm.AddEventExcluding(fallEvent, myState);


            //find built in recover state
            KFSMState recoverState = null;
            Type t = typeof(KerbalFSM);
            var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

            foreach (var field in t.GetFields(flags))
            {
                if (field.DeclaringType == t && field.Name == "States")
                {
                    List<KFSMState> list = field.GetValue(kerbalEva.fsm) as List<KFSMState>;
                    foreach (KFSMState state in list)
                    {
                        if (state != null && state.name == "Recover")
                        {
                            recoverState = state;
                            break;
                        }

                    }
                }
                if (recoverState != null)
                    break;
            }

            //make event that sends kerbal to recover state from fall down generated state
            recoverEvent = new KFSMEvent("GetBackUp");
            recoverEvent.GoToStateOnEvent = recoverState;
            recoverEvent.updateMode = KFSMUpdateMode.MANUAL_TRIGGER;
            kerbalEva.fsm.AddEvent(recoverEvent, myState);


            //setup variables based on loaded data
            if (ReviveTime != 0)
            {
                print("EvaOxygen : Out of oxygen, revive state");
                OutOfOxygen(false);

                //dont set alarm, already past needing it
                FirstUpdate = false;
            }
        }

        public override void OnUpdate()
        {
            base.OnUpdate();

            if (FirstUpdate)
            {
                ourManager.SetAlarm(this, GetAlarmValue());
            }
            FirstUpdate = false;

            ////if atmosphere is present, restore full oxygen and hold it there
            ////atmos .1 is roughly 12,500 meters above kerbin when ui goes from light blue to med blue
            //if (FlightGlobals.ActiveVessel.atmDensity >= .1)
            //{
            //    LastMissionTime = this.vessel.missionTime;
            //    FreezeTime = true;
            //    ReviveActive = false;
            //    ReviveTime = 0;
            //}
            ////no atmosphere and freeze is active, unfreeze
            //else if (FreezeTime == true && FlightGlobals.ActiveVessel.atmDensity < .1)
            //{
            //    FreezeTime = false;
            //}

            //get mission time
            double currentTime = this.vessel.missionTime - LastMissionTime;

            if (ReviveActive)
            {            
                //convert seconds to format for gui display
                ReviveTime = Convert(ms_nStartRevive, (int)currentTime);

                //didnt revive in time, die...
                if (ReviveTime <= 0)
                {
                    this.part.explode();
                    this.part.Die();

                    this.vessel.Die();
                }
            }
            else
            {
                //convert seconds to format for gui display
                OxygenTime = Convert(ms_nStartOxygen, (int)currentTime);

                //ran out of oxygen, go to revive mode
                if (OxygenTime <= 0)
                {
                    print("EvaOxygen : Out of oxygen, revive state");
                    OutOfOxygen(true);
                }
            }
        }

        public void OutOfOxygen(bool resetTime)
        {
            if (TimeWarp.CurrentRateIndex != 0)
            {
                TimeWarp.SetRate(0, true);
            }

            this.Fields["OxygenTime"].guiActive = false;
            this.Fields["ReviveTime"].guiActive = true;

            ReviveActive = true;
            if (resetTime)
            {
                LastMissionTime = this.vessel.missionTime;
            }

            Events["Revive"].active = true;

            kerbalEva.animation.Play(kerbalEva.Animations.idle.GetAnimationString());
            kerbalEva.fsm.RunEvent(fallEvent);
        }

        public int GetAlarmValue()
        {
            if (ReviveActive == false)
                return (int)((ms_nStartOxygen - (this.vessel.missionTime - LastMissionTime)) - ms_nPreAlarm);
            return EvaBehaviour.InvalidTimer;
        }

        public double HalfOxygen()
        {
            //halve your oxygen and return the time now left
            double currentTime = ms_nStartOxygen - (this.vessel.missionTime - LastMissionTime);
            double halfTime = currentTime / 2.0;

            LastMissionTime -= halfTime;

            return halfTime;
        }

        public void SetOxygen(double oxygen)
        {
            //set oxygen left to parameter
            LastMissionTime = oxygen + ((ms_nStartOxygen + (-this.vessel.missionTime)) * -1);
        }

        public void StateEnter(KFSMState oldState)
        {
            print("EvaOxygen : StateEnter");
            kerbalEva.isRagdoll = true;
            kerbalEva.canRecover = false;
        }

        public void StateUpdate()
        {
        }

        public void StateLeave(KFSMState newState)
        {
            print("EvaOxygen : StateLeave to " + newState.name);
            kerbalEva.isRagdoll = false;
            kerbalEva.canRecover = true;
        }


        //convert seconds to format hmmss
        int Convert(int max, int seconds)
        {
            int hours = 0;
            int minutes = 0;
            seconds = max - seconds;
            while (seconds >= 60)
            {
                seconds -= 60;
                minutes++;
            }
            while (minutes >= 60)
            {
                minutes -= 60;
                hours++;
            }
            return hours * 10000 + minutes * 100 + seconds;
        }
    }
}
